package br.com.educa.log;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.ResponseEntity;

import flexjson.JSONDeserializer;
import flexjson.JSONSerializer;

import br.com.educa.modelo.security.Perfil;
import br.com.educa.modelo.security.Usuario;
import br.com.educa.util.MethodUtils;
import br.com.educa.util.ReflectionUtils;

@Aspect
@SuppressWarnings({"unused","rawtypes"})
public class LogAspect {

	
	@Autowired
	WebApplicationContext wac;

	//POINT CUT para annotation @LogTrace
	@Around("within(@org.springframework.stereotype.Controller *) && execution(public ResponseEntity<String> br.gov.mj.depaid..*(.., @LogTrace (*), ..)) && @annotation(reqMap)")
	public Object pointCutAnnotation(ProceedingJoinPoint pjp, RequestMapping reqMap) throws Throwable {
		Object o = pjp.proceed();
		logActionPostPut(pjp,reqMap);
		return o;
	}

	//POINT CUT para o metodo DELETE
	@Around("within(@org.springframework.stereotype.Controller *) && execution(public ResponseEntity<String> br.gov.mj.depaid..*(.., @PathVariable (*), ..)) && @annotation(reqMap)")
	public Object pointCutDelete(ProceedingJoinPoint pjp, RequestMapping reqMap) throws Throwable {
		logActionDelete(pjp,reqMap);
		Object o = pjp.proceed();
		return o;
	}
	
	//POINT CUT para o metodo POST ou PUT
	@Around("within(@org.springframework.stereotype.Controller *) && execution(public ResponseEntity<String> br.gov.mj.depaid..*(.., @RequestBody (*), ..)) && @annotation(reqMap)")
	public Object pointCutPostPut(ProceedingJoinPoint pjp, RequestMapping reqMap) throws Throwable {
		Object o = pjp.proceed();
		logActionPostPut(pjp,reqMap);
		return o;
	}

	@Transactional(rollbackFor=Throwable.class)
	private void logActionDelete(ProceedingJoinPoint pjp,RequestMapping reqMap) throws Exception {
		
		String jsonDeletado = null;
		Usuario usuario = null;
		String ip = null;
		String porta = null;
		String requestMetodo = null;
		Class classe = null;

		//verificando se e requisicao de json
		boolean jsonRequest = false;
		String[] headers = reqMap.headers();
		for (int i = 0; i < headers.length; i++) { if(headers[i].equalsIgnoreCase("Accept=application/json")){ jsonRequest=true; break;}}
		if(jsonRequest){

			//obtendo metodo da requisicao se nao for DELETE retorna.
			RequestMethod[] requestMetodos = reqMap.method(); 
			for (int i = 0; i < requestMetodos.length; i++) {
				if(requestMetodos[i]==RequestMethod.DELETE ){
					requestMetodo="DELETE";
				}
			}
			if(!requestMetodo.equalsIgnoreCase("DELETE")){ return;}

			//obtendo usuario e ip
			Class superclassTarget = pjp.getTarget().getClass().getSuperclass();
			Method m[] = superclassTarget.getDeclaredMethods();
			for (Method method : m) {
				if(method.getName().equals("obterUsuario")){
					usuario = (Usuario) method.invoke(pjp.getTarget(),new Object[0]);
				}
				if(method.getName().equals("obterIP")){
					ip= (String) method.invoke(pjp.getTarget(),new Object[0]);
				}
				if(method.getName().equals("obterPorta")){
					porta= (String) method.invoke(pjp.getTarget(),new Object[0]);
				}
			}

			//obtendo classe
			Class classTarget = pjp.getTarget().getClass();
			Method metodos[] = classTarget.getDeclaredMethods();
			for (Method method : metodos) {
				if(method.getName().equals("create")){
					final Annotation[][] anotacoesDosParametros  = method.getParameterAnnotations();
					final Class<?>[] tipoDosParametros = method.getParameterTypes();
					for(int i = 0; i < anotacoesDosParametros.length; i++){
						final Annotation[] anotacoesDeUmParametro = anotacoesDosParametros[i];
						final Valid anotacaoProcurada = getAnnotationByType(anotacoesDeUmParametro, Valid.class);
						if(anotacaoProcurada != null){
							classe = tipoDosParametros[i];
						}
					}
				}
			}

			//obtendo id deletado
			Long id = null;
			final Signature assinatura = pjp.getSignature();
			Object[] argumentos = pjp.getArgs();
			if(assinatura instanceof MethodSignature){
				final MethodSignature assinaturaDoMetodo = (MethodSignature) assinatura;
				final Method metodo = assinaturaDoMetodo.getMethod();
				final String[] nomeDosParametros = assinaturaDoMetodo.getParameterNames();
				final Class<?>[] tipoDosParametros = assinaturaDoMetodo.getParameterTypes();
				final Annotation[][] anotacoesDosParametros = metodo.getParameterAnnotations();
				for(int i = 0; i < anotacoesDosParametros.length; i++){
					final Annotation[] anotacoesDeUmParametro = anotacoesDosParametros[i];
					final PathVariable anotacaoProcurada = getAnnotationByType(anotacoesDeUmParametro, PathVariable.class);
					if(anotacaoProcurada != null){
						id = (Long) tipoDosParametros[i].cast(argumentos[i]);
					}
				}
			}
			
			//obtendo objeto anterior
			ReflectionUtils reflectionUtils = new ReflectionUtils(classe.getCanonicalName());
			JSONSerializer serializador = new JSONSerializer();
	        jsonDeletado = serializador.serialize(reflectionUtils.findById(id));
		        

			//salvando log
			Log log = new Log();
			log.setClasse(classe.getCanonicalName());
			log.setDataRegistro(new Date());
			log.setRegistroAnterior(jsonDeletado);
			log.setTipoDeRequest(requestMetodo);
			log.setUsuario(usuario);
			log.persist();
		}
	}
	
	@Transactional(rollbackFor=Throwable.class)
	private void logActionPostPut(ProceedingJoinPoint pjp,RequestMapping reqMap) throws Exception {

		String json = null;
		String jsonAntigo = null;
		Usuario usuario = null;
		String ip = null;
		String porta = null;
		String requestMetodo = null;
		Class classe = null;

		//verificando se e requisicao de json
//		String[] headers = reqMap.headers();
//		boolean jsonRequest = false;
//		for (int i = 0; i < headers.length; i++) { if(headers[i].equalsIgnoreCase("Accept=application/json")){ jsonRequest=true; break;}}
//		if(jsonRequest){

			//obtendo metodo da requisicao
			RequestMethod[] requestMetodos = reqMap.method(); 
			for (int i = 0; i < requestMetodos.length; i++) {
				if(requestMetodos[i]==RequestMethod.POST ){
					requestMetodo="POST";
					//excessao para o caso de multipart q so aceita post
					if(pjp.getSignature().getName().contains("update")){
						requestMetodo="PUT";
					}
				}
				if(requestMetodos[i]==RequestMethod.PUT ){
					requestMetodo="PUT";
				}
			}

			//obtendo usuario e ip
			Class superclassTarget = pjp.getTarget().getClass().getSuperclass();
			Method m[] = superclassTarget.getDeclaredMethods();
			for (Method method : m) {
				if(method.getName().equals("obterUsuario")){
					usuario = (Usuario) method.invoke(pjp.getTarget(),new Object[0]);
				}
				if(method.getName().equals("obterIP")){
					ip= (String) method.invoke(pjp.getTarget(),new Object[0]);
				}
				if(method.getName().equals("obterPorta")){
					porta= (String) method.invoke(pjp.getTarget(),new Object[0]);
				}
			}

			//obtendo classe
			Class classTarget = pjp.getTarget().getClass();
			Method metodos[] = classTarget.getDeclaredMethods();
			for (Method method : metodos) {
				if(method.getName().equals("create")){
					final Annotation[][] anotacoesDosParametros  = method.getParameterAnnotations();
					final Class<?>[] tipoDosParametros = method.getParameterTypes();
					for(int i = 0; i < anotacoesDosParametros.length; i++){
						final Annotation[] anotacoesDeUmParametro = anotacoesDosParametros[i];
						final Valid anotacaoProcurada = getAnnotationByType(anotacoesDeUmParametro, Valid.class);
						if(anotacaoProcurada != null){
							classe = tipoDosParametros[i];
						}
					}
				}
			}

			//obtendo json
			final Signature assinatura = pjp.getSignature();
			Object[] argumentos = pjp.getArgs();
			if(assinatura instanceof MethodSignature){
				final MethodSignature assinaturaDoMetodo = (MethodSignature) assinatura;
				final Method metodo = assinaturaDoMetodo.getMethod();
				final String[] nomeDosParametros = assinaturaDoMetodo.getParameterNames();
				final Class<?>[] tipoDosParametros = assinaturaDoMetodo.getParameterTypes();
				final Annotation[][] anotacoesDosParametros = metodo.getParameterAnnotations();
				for(int i = 0; i < anotacoesDosParametros.length; i++){
					final Annotation[] anotacoesDeUmParametro = anotacoesDosParametros[i];
					final RequestBody anotacaoProcurada = getAnnotationByType(anotacoesDeUmParametro, RequestBody.class);
					final LogTrace anotacaoProcuradaLogTrace = getAnnotationByType(anotacoesDeUmParametro, LogTrace.class);
					if(anotacaoProcurada != null){
						json = (String) tipoDosParametros[i].cast(argumentos[i]);
					}else if(anotacaoProcuradaLogTrace !=null){
						json = (String) tipoDosParametros[i].cast(argumentos[i]);
					}
				}
			}
			
			//obtendo objeto anterior
			if(requestMetodo.equals("PUT")){
				ReflectionUtils reflectionUtils = new ReflectionUtils(classe.getCanonicalName());
				JSONSerializer serializador = new JSONSerializer();
		        JSONDeserializer deserializador = new JSONDeserializer();
		        Object objetoTranscrito = deserializador.deserialize(json);
		        @SuppressWarnings("unchecked")
				HashMap<String, Object> hashMap = (HashMap<String, Object>) objetoTranscrito;
		        Long id = Long.parseLong(hashMap.get("id").toString());
		        jsonAntigo = serializador.serialize(reflectionUtils.findById(id));
		        
			}

			//salvando log
			Log log = new Log();
			log.setClasse(classe.getCanonicalName());
			log.setDataRegistro(new Date());
			log.setRegistro(json);
			log.setRegistroAnterior(jsonAntigo);
			log.setTipoDeRequest(requestMetodo);
			log.setUsuario(usuario);
			log.setIp(ip);
			log.setPorta(porta);
			log.persist();
//		}
	}

//	@Transactional(rollbackFor=Throwable.class)
//	private void logActionAnnotation(ProceedingJoinPoint pjp,LogTrace logTrace) throws Exception {
//
//		String json = null;
//		String jsonAntigo = null;
//		Usuario usuario = null;
//		String ip = null;
//		String porta = null;
//		String requestMetodo = null;
//		Class classe = null;
//
//		//obtendo metodo da requisicao
//		String[] requestMetodos = logTrace.requestMethod();
//		requestMetodo=requestMetodos[0];
//
//		//obtendo usuario e ip
//		Class superclassTarget = pjp.getTarget().getClass().getSuperclass();
//		Method m[] = superclassTarget.getDeclaredMethods();
//		for (Method method : m) {
//			if(method.getName().equals("obterUsuario")){
//				usuario = (Usuario) method.invoke(pjp.getTarget(),new Object[0]);
//			}
//			if(method.getName().equals("obterIP")){
//				ip= (String) method.invoke(pjp.getTarget(),new Object[0]);
//			}
//			if(method.getName().equals("obterPorta")){
//				porta= (String) method.invoke(pjp.getTarget(),new Object[0]);
//			}
//		}
//
//		//obtendo classe
//		Class classTarget = pjp.getTarget().getClass();
//		Method metodos[] = classTarget.getDeclaredMethods();
//		for (Method method : metodos) {
//			if(method.getName().equals("create")){
//				final Annotation[][] anotacoesDosParametros  = method.getParameterAnnotations();
//				final Class<?>[] tipoDosParametros = method.getParameterTypes();
//				for(int i = 0; i < anotacoesDosParametros.length; i++){
//					final Annotation[] anotacoesDeUmParametro = anotacoesDosParametros[i];
//					final Valid anotacaoProcurada = getAnnotationByType(anotacoesDeUmParametro, Valid.class);
//					if(anotacaoProcurada != null){
//						classe = tipoDosParametros[i];
//					}
//				}
//			}
//		}
//
//		//obtendo json
//		final Signature assinatura = pjp.getSignature();
//		Object[] argumentos = pjp.getArgs();
//		if(assinatura instanceof MethodSignature){
//			final MethodSignature assinaturaDoMetodo = (MethodSignature) assinatura;
//			final Method metodo = assinaturaDoMetodo.getMethod();
//			final String[] nomeDosParametros = assinaturaDoMetodo.getParameterNames();
//			final Class<?>[] tipoDosParametros = assinaturaDoMetodo.getParameterTypes();
//			for(int i = 0; i < nomeDosParametros.length; i++){
//				if(nomeDosParametros.equals(logTrace.fieldName()[0])){
//					json = (String) tipoDosParametros[i].cast(argumentos[i]);
//				}
//			}
//		}
//		
//		//obtendo objeto anterior
//		if(requestMetodo.equals("PUT")){
//			ReflectionUtils reflectionUtils = new ReflectionUtils(classe.getCanonicalName());
//			JSONSerializer serializador = new JSONSerializer();
//	        JSONDeserializer deserializador = new JSONDeserializer();
//	        Object objetoTranscrito = deserializador.deserialize(json);
//	        @SuppressWarnings("unchecked")
//			HashMap<String, Object> hashMap = (HashMap<String, Object>) objetoTranscrito;
//	        Long id = Long.parseLong(hashMap.get("id").toString());
//	        jsonAntigo = serializador.serialize(reflectionUtils.findById(id));
//	        
//		}
//
//		//salvando log
//		Log log = new Log();
//		log.setClasse(classe.getCanonicalName());
//		log.setDataRegistro(new Date());
//		log.setRegistro(json);
//		log.setRegistroAnterior(jsonAntigo);
//		log.setTipoDeRequest(requestMetodo);
//		log.setUsuario(usuario);
//		log.setIp(ip);
//		log.setPorta(porta);
//		log.persist();
//	}

	@SuppressWarnings("unchecked")
	private static <T extends Annotation> T getAnnotationByType(final Annotation[] annotations, final Class<T> clazz){

		T result = null;
		for(final Annotation annotation : annotations){
			if(clazz.isAssignableFrom(annotation.getClass())){
				result = (T) annotation;
				break;
			}
		}
		return result;
	}

	private void processParameter(final String signature, final String paramName, final Class<?> paramType, final RequestBody paramAnnotation){

		System.out.println(MessageFormat.format(
				"Found parameter ''{0}'' \n  of type ''{1}'' \n  with annotation ''{2}'' \n  in method ''{3}''",
				paramName,
				paramType,
				paramAnnotation,
				signature));
	}


}






